#ifndef SOCKET_H
#define SOCKET_H
extern "C" {
#include <sys/socket.h>
#include <string.h>
}
#include <string>
#include <vector>
#include <system_error>


class socket
{
public:
    struct address {
        address() {}
        address(address&& addr) :host(std::move(addr.host)), port(addr.port) {}
        address& operator=(address&& addr) { host=std::move(addr.host); port = addr.port; return *this; }
        std::string host;
        uint16_t port = 0;
    };
    socket(int fd=-1);
    socket(int family, int type, int protocol);
    socket(const socket&)=delete;
    socket(socket&&);
    socket& operator=(class socket&&sock);
    virtual~socket();

    inline int fileno()const { return fd; }
    inline bool valid()const noexcept { return fd != -1; }

    template<typename _T=int> inline void setsockopt(int level, int optname, const _T& val) {
        int ret;
        ret = ::setsockopt(fd, level, optname, static_cast<const void*>(&val), sizeof(val));
        if(ret == -1) throw std::system_error(errno,
                                              std::system_category(),
                                              std::string("SetSockOpt Failure: ") + strerror(errno));
    }
    template<typename _T=int> inline _T getsockopt(int level, int optname) {
        int ret;
        _T result;
        ret = ::getsockopt(fd, level, optname, static_cast<void*>(&result), sizeof(result));
        if(ret == -1) throw std::system_error(errno,
                                              std::system_category(),
                                              std::string("GetSockOpt Failure: ") + strerror(errno));

        return result;
    }

    void bind(const std::string& host, uint16_t port);
    void listen(int backlog=1024);
    void connect(const std::string& host, uint16_t port);

    socket accept();
    inline int send(const void *buffer, int size, int flag=0) const { return ::send(fd, buffer, size, flag); }
    inline int recv(void *buffer, int size, int flag=0) const { return ::recv(fd, buffer, size, flag); }

    void open(int family, int type, int protocol);
    void close();
    void shutdown(int flag);
    void set_block(bool _block=true);
    bool readable();
    bool writeable();

    //  convenience function
    inline int send(const std::vector<uint8_t>& buffer) {
        return this->send(static_cast<const void*>(buffer.data()), buffer.size());
    }
    inline int recv(std::vector<uint8_t>&buffer) {
        return this->recv(static_cast<void*>(buffer.data()), buffer.size());
    }
    std::vector<uint8_t> recv(int size=-1);

    address local, remote;
private:
    int fd;
};

#endif // SOCKET_H
